package com.heima.article.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.article.mapper.ApArticleConfigMapper;
import com.heima.article.mapper.ApArticleContentMapper;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.article.service.ApArticleService;
import com.heima.article.service.ArticleFreemarkerService;
import com.heima.common.constants.ArticleConstants;
import com.heima.common.constants.UserConstants;
import com.heima.common.redis.CacheService;
import com.heima.model.article.dtos.ArticleDto;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.article.dtos.ArticleInfoDto;
import com.heima.model.article.mess.ArticleVisitStreamMess;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.article.pojos.ApArticleConfig;
import com.heima.model.article.pojos.ApArticleContent;
import com.heima.model.article.vos.ApArticleVo;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.utils.thread.WeMediaThreadLocalUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
@Transactional
public class ApArticleServiceImpl extends ServiceImpl<ApArticleMapper, ApArticle> implements ApArticleService {
    @Autowired
    private ApArticleMapper apArticleMapper;
    @Autowired
    private ApArticleConfigMapper apArticleConfigMapper;
    @Autowired
    private ApArticleContentMapper apArticleContentMapper;
    @Autowired
    private ArticleFreemarkerService articleFreemarkerService;
    @Autowired
    private CacheService cacheService;

    /**
     * 文章首页加载缓存优化
     * */
    @Override
    public ResponseResult loadFirst(Short type, ArticleHomeDto dto) {
        //文章首页加载优先从缓存中读取数据
        String data = cacheService.get(ArticleConstants.HOT_ARTICLE_FIRST_PAGE + dto.getTag());
        if(StringUtils.isNotEmpty(data)){
            List<ApArticleVo> apArticleVoList = JSON.parseObject(data, List.class);
            return ResponseResult.okResult(apArticleVoList);
        }else
            //缓存中没有则从数据库中读取
            return load(type, dto);
    }


    /**
     * 文章列表加载
     */
    @Override
    public ResponseResult load(Short type, ArticleHomeDto dto) {
        //1、参数校验
        if (type == null && dto == null)
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);

        //2、数据处理
        //2.1如果size前端没传，给个默认值：10；如果前端传了，判断是否过大，过大取限定值：50
        Integer size = dto.getSize();
        if (size == null || size == 0) size = 10;
        size = Math.min(size, 50);
        dto.setSize(size);
        //2.2如果tag没给，设置默认频道"_all_"
        if (StringUtils.isEmpty(dto.getTag())) dto.setTag(ArticleConstants.DEFAULT_TAG);
        //2.3如果最大时间和最小时间前端没给，设置为当前系统时间
        if (dto.getMaxBehotTime() == null) dto.setMaxBehotTime(new Date());
        if (dto.getMinBehotTime() == null) dto.setMinBehotTime(new Date());
        //2.4如果type既不是1也不是2，设置默认加载首页，也就是加载更多
        if (type != ArticleConstants.LOADTYPE_LOAD_MORE && type != ArticleConstants.LOADTYPE_LOAD_NEW)
            type = ArticleConstants.LOADTYPE_LOAD_MORE;

        //3、业务操作
        List<ApArticle> apArticles = apArticleMapper.loadArticleList(dto, type);

        //4、返回结果
        return ResponseResult.okResult(apArticles);
    }



    /**
     * app端文章保存
     */
    @Override
    public ResponseResult saveArticle(ArticleDto dto) {
        //1.检查参数
        if (dto == null || StringUtils.isEmpty(dto.getContent())) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        //数据处理
        ApArticle apArticle = new ApArticle();
        BeanUtils.copyProperties(dto, apArticle);

        //2.判断是否存在id
        if (dto.getId() == null) {
            //2.1 id不存在:保存  文章、文章配置、文章内容
            //保存文章
            save(apArticle);

            //保存配置
            ApArticleConfig apArticleConfig = new ApArticleConfig(apArticle.getId());
            apArticleConfigMapper.insert(apArticleConfig);

            //保存内容
            ApArticleContent apArticleContent = new ApArticleContent();
            apArticleContent.setArticleId(apArticle.getId());
            apArticleContent.setContent(dto.getContent());
            apArticleContentMapper.insert(apArticleContent);

        } else {
            //2.2 id存在：修改  文章、文章内容
            //修改文章
            updateById(apArticle);

            //修改内容
            ApArticleContent apArticleContent = apArticleContentMapper.selectOne(Wrappers.<ApArticleContent>lambdaQuery().eq(ApArticleContent::getArticleId, dto.getId()));
            apArticleContent.setContent(dto.getContent());
            apArticleContentMapper.updateById(apArticleContent);
        }

        //异步调用 生成静态文件上传到minIo中
        articleFreemarkerService.buildArticleToMinIO(apArticle, dto.getContent());

        //3.结果返回  文章的id
        return ResponseResult.okResult(apArticle.getId());
    }


    /**
     * 更新article表的相应字段
     */
    @Override
    public void updateByMap(Map map) {
        //获取文章对象
        Long articleId = (Long) map.get("articleId");
        ApArticle article = getById(articleId);
        if(article == null) return;
        //属性字段
        String type = (String) map.get("type");

        /*
         * 点赞数量
         * */
        if (type.equals("likes")) {
            //0 点赞  1 取消点赞
            Integer operation = (Integer) map.get("operation");
            if(article.getLikes() == null || article.getLikes() < 0) article.setLikes(0);
            if (operation == 0) {//点赞
                article.setLikes(article.getLikes() + 1);
            } else article.setLikes(article.getLikes() - 1); //取消点赞
        }
        /*
         * 阅读数量
         * */
        if (type.equals("read")) {
            if(article.getViews() == null || article.getViews() < 0) article.setViews(0);
            article.setViews(article.getViews() + 1);
        }
        /*
         * 收藏数量
         * */
        if (type.equals("collection")) {
            //0 收藏  1 取消收藏
            Integer operation = (Integer) map.get("operation");
            if(article.getCollection() == null || article.getCollection() < 0) article.setCollection(0);
            if (operation == 0) {
                article.setCollection(article.getCollection() + 1);
            } else article.setCollection(article.getCollection() - 1);
        }

        updateById(article);
    }


    /**
     * 加载文章行为-数据回显
     */
    @Override
    public ResponseResult loadArticle(ArticleInfoDto dto) {
        //1.参数校验
        if (dto == null || dto.getArticleId() == null || dto.getAuthorId() == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }

        //2.业务处理
        boolean islike = false, isunlike = false, iscollection = false, isfollow = false;
        //需要用户登录
        Integer userId = WeMediaThreadLocalUtil.get();
        if (userId == null)
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST);

        //喜欢行为
        String likeBehavior = (String) cacheService.hGet(UserConstants.LIKES_BEHAVIOR + dto.getArticleId().toString(), userId.toString());
        if (StringUtils.isNotBlank(likeBehavior)) {
            islike = true;
        }
        //不喜欢的行为
        String unLikeBehavior = (String) cacheService.hGet(UserConstants.UNLIKES_BEHAVIOR + dto.getArticleId().toString(), userId.toString());
        if (StringUtils.isNotBlank(unLikeBehavior)) {
            isunlike = true;
        }
        //是否收藏
        String collction = (String) cacheService.hGet(UserConstants.COLLECTION_BEHAVIOR + dto.getArticleId().toString(), userId.toString());
        if (StringUtils.isNotBlank(collction)) {
            iscollection = true;
        }

        //是否关注
        Double follow = cacheService.zScore(UserConstants.APUSER_FOLLOW + userId.toString(), dto.getAuthorId().toString());
        if (follow != null) {
            isfollow = true;
        }


        //3.封装数据返回
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("islike", islike);
        resultMap.put("isunlike", isunlike);
        resultMap.put("iscollection", iscollection);
        resultMap.put("isfollow", isfollow);

        return ResponseResult.okResult(resultMap);
    }


    /**
     * 更新热点文章分值及修改文章行为数据
     * @param mess
     */
    @Override
    public void updateScore(ArticleVisitStreamMess mess) {
        //0. 先拿到更新前的行为分值
        ApArticle apArticle = apArticleMapper.selectById(mess.getArticleId());
        Integer oldScore = computeScore(apArticle);

        //1. 更新数据库中文章的行为数据
        ApArticle article = updateArticle(mess);

        //2. 对当前文章重新算分
        Integer scoreToday = computeScore(article);
        scoreToday *= 3;        //当日热度权重整体 * 3

        Integer score = oldScore + scoreToday;

        //3. 更新对应频道的热点文章
        replaceDataToRedis(article, score, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + apArticle.getChannelId());

        //4. 更新推荐频道的热点文章
        replaceDataToRedis(article, score, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + ArticleConstants.DEFAULT_TAG);
    }

    /**
     * 替换数据并且存入到redis
     */
    private void replaceDataToRedis(ApArticle apArticle, Integer score, String channelId) {
        //获取对应频道数据
        String articleListStr = cacheService.get(channelId);
        if (StringUtils.isNotBlank(articleListStr)) {
            List<ApArticleVo> hotArticleVoList = JSON.parseArray(articleListStr, ApArticleVo.class);

            boolean flag = true;

            //如果缓存中存在该文章，只更新分值
            for (ApArticleVo hotArticleVo : hotArticleVoList) {
                if (hotArticleVo.getId().equals(apArticle.getId())) {
                    hotArticleVo.setScore(score);
                    flag = false;
                    break;
                }
            }

            //如果缓存中不存在，查询缓存中分值最小的一条数据，进行分值的比较，如果当前文章的分值大于缓存中的数据，就替换
            if (flag) {
                //热点文章数量大于30条就和最后一条数据比较分值
                if (hotArticleVoList.size() >= 30) {
                    hotArticleVoList = hotArticleVoList.stream().sorted((o1, o2) -> o2.getScore() - o1.getScore()).collect(Collectors.toList());
                    ApArticleVo lastHot = hotArticleVoList.get(hotArticleVoList.size() - 1);
                    if (lastHot.getScore() < score) {
                        hotArticleVoList.remove(lastHot);
                        ApArticleVo hot = new ApArticleVo();
                        BeanUtils.copyProperties(apArticle, hot);
                        hot.setScore(score);
                        hotArticleVoList.add(hot);
                    }
                } else {
                    //热点文章数量小于30条直接插入
                    ApArticleVo hot = new ApArticleVo();
                    BeanUtils.copyProperties(apArticle, hot);
                    hot.setScore(score);
                    hotArticleVoList.add(hot);
                }
            }

            //对热点文章重新降序排序
            hotArticleVoList = hotArticleVoList.stream().sorted((o1, o2) -> o2.getScore() - o1.getScore()).collect(Collectors.toList());
            //缓存到redis
            cacheService.set(channelId, JSON.toJSONString(hotArticleVoList));
        }else {
            //缓存中没有数据直接存入
            ApArticleVo hot = new ApArticleVo();
            BeanUtils.copyProperties(apArticle, hot);
            hot.setScore(score);
            cacheService.set(channelId,JSON.toJSONString(hot));
        }
    }

    /**
     * 更新文章行为数量
     */
    private ApArticle updateArticle(ArticleVisitStreamMess mess) {
        ApArticle apArticle = getById(mess.getArticleId());
        apArticle.setCollection(apArticle.getCollection()==null ? mess.getCollect() : apArticle.getCollection()+mess.getCollect());
        apArticle.setComment(apArticle.getComment()==null ? mess.getComment() : apArticle.getComment()+mess.getComment());
        apArticle.setLikes(apArticle.getLikes()==null ? mess.getLike() : apArticle.getLikes()+mess.getLike());
        apArticle.setViews(apArticle.getViews()==null ? mess.getView() : apArticle.getViews()+mess.getView());
        updateById(apArticle);
        return apArticle;

    }

    /**
     * 计算文章的具体分值
     */
    private Integer computeScore(ApArticle apArticle) {
        Integer score = 0;
        if(apArticle.getLikes() != null){
            score += apArticle.getLikes() * ArticleConstants.HOT_ARTICLE_LIKE_WEIGHT;
        }
        if(apArticle.getViews() != null){
            score += apArticle.getViews();
        }
        if(apArticle.getComment() != null){
            score += apArticle.getComment() * ArticleConstants.HOT_ARTICLE_COMMENT_WEIGHT;
        }
        if(apArticle.getCollection() != null){
            score += apArticle.getCollection() * ArticleConstants.HOT_ARTICLE_COLLECTION_WEIGHT;
        }

        return score;
    }

}

